// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.net;

import androidx.annotation.Nullable;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.Executor;

/**
 * Information about a finished request. Passed to {@link RequestFinishedInfo.Listener}.
 *
 * <p>To associate the data with the original request, use {@link
 * UrlRequest.Builder#addRequestAnnotation} to add a unique identifier when creating the
 * request, and call {@link #getAnnotations} when the {@link RequestFinishedInfo} is received to
 * retrieve the identifier.
 */
public abstract class RequestFinishedInfo {
    /** Listens for finished requests for the purpose of collecting metrics. */
    public abstract static class Listener {
        private final Executor mExecutor;

        public Listener(Executor executor) {
            if (executor == null) {
                throw new IllegalStateException("Executor must not be null");
            }
            mExecutor = executor;
        }

        /**
         * Invoked with request info. Will be called in a task submitted to the {@link
         * java.util.concurrent.Executor} returned by {@link #getExecutor}.
         *
         * @param requestInfo {@link RequestFinishedInfo} for finished request.
         */
        public abstract void onRequestFinished(RequestFinishedInfo requestInfo);

        /**
         * Returns this listener's executor. Can be called on any thread.
         *
         * @return this listener's {@link java.util.concurrent.Executor}
         */
        public Executor getExecutor() {
            return mExecutor;
        }
    }

    /**
     * Metrics collected for a single request. Most of these metrics are timestamps for events
     * during the lifetime of the request, which can be used to build a detailed timeline for
     * investigating performance.
     *
     * <p>Events happen in this order:
     *
     * <ol>
     *   <li>{@link #getRequestStart request start}
     *   <li>{@link #getDnsStart DNS start}
     *   <li>{@link #getDnsEnd DNS end}
     *   <li>{@link #getConnectStart connect start}
     *   <li>{@link #getSslStart SSL start}
     *   <li>{@link #getSslEnd SSL end}
     *   <li>{@link #getConnectEnd connect end}
     *   <li>{@link #getSendingStart sending start}
     *   <li>{@link #getSendingEnd sending end}
     *   <li>{@link #getResponseStart response start}
     *   <li>{@link #getRequestEnd request end}
     * </ol>
     *
     * Start times are reported as the time when a request started blocking on event, not when the
     * event actually occurred, with the exception of push start and end. If a metric is not
     * meaningful or not available, including cases when a request finished before reaching that
     * stage, start and end times will be {@code null}. If no time was spent blocking on an event,
     * start and end will be the same time.
     *
     * <p>If the system clock is adjusted during the request, some of the {@link java.util.Date}
     * values might not match it. Timestamps are recorded using a clock that is guaranteed not to
     * run backwards. All timestamps are correct relative to the system clock at the time of request
     * start, and taking the difference between two timestamps will give the correct difference
     * between the events. In order to preserve this property, timestamps for events other than
     * request start are not guaranteed to match the system clock at the times they represent.
     *
     * <p>Most timing metrics are taken from <a
     * href="https://cs.chromium.org/chromium/src/net/base/load_timing_info.h">LoadTimingInfo</a>,
     * which holds the information for <a href="http://w3c.github.io/navigation-timing/"></a> and <a
     * href="https://www.w3.org/TR/resource-timing/"></a>.
     *
     * <p>{@hide} as it's a prototype.
     */
    public abstract static class Metrics {
        /**
         * Returns time when the request started.
         *
         * @return {@link java.util.Date} representing when the native request actually started.
         *         This
         *     timestamp will match the system clock at the time it represents.
         */
        @Nullable
        public abstract Date getRequestStart();

        /**
         * Returns time when DNS lookup started. This and {@link #getDnsEnd} will return non-null
         * values regardless of whether the result came from a DNS server or the local cache.
         *
         * @return {@link java.util.Date} representing when DNS lookup started. {@code null} if the
         *     socket was reused (see {@link #getSocketReused}).
         */
        @Nullable
        public abstract Date getDnsStart();

        /**
         * Returns time when DNS lookup finished. This and {@link #getDnsStart} will return non-null
         * values regardless of whether the result came from a DNS server or the local cache.
         *
         * @return {@link java.util.Date} representing when DNS lookup finished. {@code null} if the
         *     socket was reused (see {@link #getSocketReused}).
         */
        @Nullable
        public abstract Date getDnsEnd();

        /**
         * Returns time when connection establishment started.
         *
         * @return {@link java.util.Date} representing when connection establishment started,
         *         typically
         *     when DNS resolution finishes. {@code null} if the socket was reused (see {@link
         *     #getSocketReused}).
         */
        @Nullable
        public abstract Date getConnectStart();

        /**
         * Returns time when connection establishment finished.
         *
         * @return {@link java.util.Date} representing when connection establishment finished, after
         *         TCP
         *     connection is established and, if using HTTPS, SSL handshake is completed. For QUIC
         *     0-RTT, this represents the time of handshake confirmation and might happen later than
         *     {@link #getSendingStart}. {@code null} if the socket was reused (see {@link
         *     #getSocketReused}).
         */
        @Nullable
        public abstract Date getConnectEnd();

        /**
         * Returns time when SSL handshake started. For QUIC, this will be the same time as {@link
         * #getConnectStart}.
         *
         * @return {@link java.util.Date} representing when SSL handshake started. {@code null} if
         *         SSL
         *     is not used or if the socket was reused (see {@link #getSocketReused}).
         */
        @Nullable
        public abstract Date getSslStart();

        /**
         * Returns time when SSL handshake finished. For QUIC, this will be the same time as {@link
         * #getConnectEnd}.
         *
         * @return {@link java.util.Date} representing when SSL handshake finished. {@code null} if
         *         SSL
         *     is not used or if the socket was reused (see {@link #getSocketReused}).
         */
        @Nullable
        public abstract Date getSslEnd();

        /**
         * Returns time when sending the request started.
         *
         * @return {@link java.util.Date} representing when sending HTTP request headers started.
         */
        @Nullable
        public abstract Date getSendingStart();

        /**
         * Returns time when sending the request finished.
         *
         * @return {@link java.util.Date} representing when sending HTTP request body finished.
         *         (Sending
         *     request body happens after sending request headers.)
         */
        @Nullable
        public abstract Date getSendingEnd();

        /**
         * Returns time when first byte of HTTP/2 server push was received.
         *
         * @return {@link java.util.Date} representing when the first byte of an HTTP/2 server push
         *         was
         *     received. {@code null} if server push is not used.
         */
        @Nullable
        public abstract Date getPushStart();

        /**
         * Returns time when last byte of HTTP/2 server push was received.
         *
         * @return {@link java.util.Date} representing when the last byte of an HTTP/2 server push
         *         was
         *     received. {@code null} if server push is not used.
         */
        @Nullable
        public abstract Date getPushEnd();

        /**
         * Returns time when the end of the response headers was received.
         *
         * @return {@link java.util.Date} representing when the end of the response headers was
         *     received.
         */
        @Nullable
        public abstract Date getResponseStart();

        /**
         * Returns time when the request finished.
         *
         * @return {@link java.util.Date} representing when the request finished.
         */
        @Nullable
        public abstract Date getRequestEnd();

        /**
         * Returns whether the socket was reused from a previous request. In HTTP/2 or QUIC, if
         * streams are multiplexed in a single connection, returns {@code true} for all streams
         * after the first.
         *
         * @return whether this request reused a socket from a previous request. When {@code true},
         *         DNS,
         *     connection, and SSL times will be {@code null}.
         */
        public abstract boolean getSocketReused();

        /**
         * Returns milliseconds between request initiation and first byte of response headers, or
         * {@code null} if not collected. TODO(mgersh): Remove once new API works
         * http://crbug.com/629194
         * {@hide}
         */
        @Nullable
        public abstract Long getTtfbMs();

        /**
         * Returns milliseconds between request initiation and finish, including a failure or
         * cancellation, or {@code null} if not collected. TODO(mgersh): Remove once new API works
         * http://crbug.com/629194 {@hide}
         */
        @Nullable
        public abstract Long getTotalTimeMs();

        /**
         * Returns total bytes sent over the network transport layer, or {@code null} if not
         * collected.
         */
        @Nullable
        public abstract Long getSentByteCount();

        /**
         * Returns total bytes received over the network transport layer, or {@code null} if not
         * collected. Number of bytes does not include any previous redirects.
         */
        @Nullable
        public abstract Long getReceivedByteCount();
    }

    /**
     * Reason value indicating that the request succeeded. Returned from {@link #getFinishedReason}.
     */
    public static final int SUCCEEDED = 0;
    /**
     * Reason value indicating that the request failed or returned an error. Returned from {@link
     * #getFinishedReason}.
     */
    public static final int FAILED = 1;
    /**
     * Reason value indicating that the request was canceled. Returned from {@link
     * #getFinishedReason}.
     */
    public static final int CANCELED = 2;

    /**
     * Returns the request's original URL.
     *
     * @return the request's original URL
     */
    public abstract String getUrl();

    /**
     * Returns the objects that the caller has supplied when initiating the request, using {@link
     * UrlRequest.Builder#addRequestAnnotation}. Annotations can be used to associate a
     * {@link RequestFinishedInfo} with the original request or type of request.
     *
     * @return annotations supplied when creating the request
     */
    public abstract Collection<Object> getAnnotations();

    // TODO(klm): Collect and return a chain of Metrics objects for redirect responses.
    // TODO(mgersh): Update this javadoc when new metrics are fully implemented

    /**
     * Returns metrics collected for this request.
     *
     * <p>The reported times and bytes account for all redirects, i.e.
     * the TTFB is from the start of the original request to the ultimate response headers, the TTLB
     * is from the start of the original request to the end of the ultimate response, the received
     * byte count is for all redirects and the ultimate response combined. These cumulative metric
     * definitions are debatable, but are chosen to make sense for user-facing latency analysis.
     *
     * @return metrics collected for this request.
     *
     * <p>{@hide} as the Metrics class is hidden
     */
    public abstract Metrics getMetrics();

    /**
     * Returns the reason why the request finished.
     *
     * @return one of {@link #SUCCEEDED}, {@link #FAILED}, or {@link #CANCELED}
     */
    public abstract int getFinishedReason();

    /**
     * Returns a {@link UrlResponseInfo} for the request, if its response had started.
     *
     * @return {@link UrlResponseInfo} for the request, if its response had started.
     */
    @Nullable
    public abstract UrlResponseInfo getResponseInfo();

    /**
     * If the request failed, returns the same {@link CronetException} provided to {@link
     * UrlRequest.Callback#onFailed}.
     *
     * @return the request's {@link CronetException}, if the request failed
     */
    @Nullable
    public abstract CronetException getException();
}
